/* NOTICE
    OpenJaWS - Open Java Weather Station
    
    Copyright (C) 2008 Grant Gardner <grant@lastweekend.com.au>
    
    OpenJaWS is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
    
NOTICE */
package au.com.lastweekend.openjaws.api;

import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import au.com.lastweekend.openjaws.units.RainfallUnit;
import au.com.lastweekend.openjaws.units.Unit;

/**
 * Simple structure for capturing readings from a WeatherStation
 * 
 * Holds current/max/min for a fixed set of readings as well as
 * a Map<String,Object> of other station values.
 * 
 * Manages an incremental rainfall count if over multiple readings.
 * 
 * All of the get methods that return other structures are protected from returning null
 * 
 *  
 * @author ggardner
 * 
 */
public class Readings {

    private static final Logger LOG = LoggerFactory.getLogger(Readings.class);

    private Date readingTime = new Date();
    private CurrentMinMax indoorTemperature = new CurrentMinMax();
    private CurrentMinMax indoorHumidity = new CurrentMinMax();
    private CurrentMinMax outdoorTemperature = new CurrentMinMax();
    private CurrentMinMax outdoorHumidity = new CurrentMinMax() ;
    private CurrentMinMax airPressure = new CurrentMinMax();
    private WindCurrentMinMax windSpeed  = new WindCurrentMinMax();
    private CurrentMinMax rain1H  = new CurrentMinMax();
    private CurrentMinMax rain24H = new CurrentMinMax();

    private UnitValue rainAbsolute;
    private UnitValue rainIncrement;

    private Map<String, Object> stationValues = new HashMap<String, Object>();

    /**
     * Copy constructor. 
     * 
     * @param readings
     */
    public Readings(Readings readings) {

        super();
        readingTime = readings.readingTime;
        indoorTemperature = new CurrentMinMax(readings.indoorTemperature);
        outdoorTemperature = new CurrentMinMax(readings.outdoorTemperature);
        indoorHumidity = new CurrentMinMax(readings.indoorHumidity);
        outdoorHumidity = new CurrentMinMax(readings.indoorHumidity);
        airPressure = new CurrentMinMax(readings.airPressure);
        windSpeed = new WindCurrentMinMax(readings.windSpeed);
        rain1H = new CurrentMinMax(readings.rain1H);
        rain24H = new CurrentMinMax(readings.rain24H);
        
        rainAbsolute = readings.rainAbsolute;
        rainIncrement = readings.rainIncrement;
    }

    public Readings() {

        super();
    }

    public String toString() {

        StringBuilder sb = new StringBuilder();
        sb.append(String.format("@ %tc", readingTime));
        sb.append("\ninT=");
        sb.append(indoorTemperature);
        sb.append("\noutT=");
        sb.append(outdoorTemperature);
        sb.append("\ninH=");
        sb.append(indoorHumidity);
        sb.append("\noutH=");
        sb.append(outdoorHumidity);
        sb.append("\nwind=");
        sb.append(windSpeed);
        sb.append("\npRel=");
        sb.append(airPressure);
        sb.append("\nr1H=");
        sb.append(rain1H);
        sb.append("\nr24H=");
        sb.append(rain24H);
        sb.append("\nrAbs=");
        sb.append(rainAbsolute);
        sb.append("\nrInc=");
        sb.append(rainIncrement);
        sb.append('\n');
        for (String key : stationValues.keySet()) {
            sb.append(key);
            sb.append('=');
            sb.append(stationValues.get(key));
            sb.append('\n');
        }

        return sb.toString();
    }

    public CurrentMinMax getIndoorTemperature() {

        return indoorTemperature;
    }

    public CurrentMinMax getIndoorHumidity() {

        return indoorHumidity;
    }

    public CurrentMinMax getOutdoorTemperature() {

        return outdoorTemperature;
    }

    public CurrentMinMax getOutdoorHumidity() {

        return outdoorHumidity;
    }

    public CurrentMinMax getAirPressure() {

        return airPressure;
    }

    public CurrentMinMax getRain1H() {

        return rain1H;
    }

    public CurrentMinMax getRain24H() {

        return rain24H;
    }

    public WindCurrentMinMax getWindSpeed() {

        return windSpeed;
    }

    public Date getReadingTime() {

        return readingTime;
    }

    public void setReadingTime(Date readingTime) {

        this.readingTime = readingTime;
    }

    /**
     * Total rain since station last reset. Also calculates rainIncrement
     * 
     * @param newRainAbsolute value of rain
     * @param unit - RainfallUnit the value is measured in.
     */
    public void setRainAbsolute(double newRainAbsolute, RainfallUnit unit) {

        setRainAbsolute(new UnitValue(newRainAbsolute, unit));

    }

    /**
     * Total rain since station last reset. Also calculates rainIncrement
     * 
     * @param newRainAbsolute
     */
    public void setRainAbsolute(UnitValue newRainAbsolute) {

        Unit unit = newRainAbsolute.getUnit();
        double rainDiff = 0;
        if (rainAbsolute != null) {
            // We could expect the unit to be the same, but this means we can change units if we want...
            rainDiff = (newRainAbsolute.getValue() - rainAbsolute.convertTo(unit).getValue());

            if (rainDiff < 0) {
                // Either initialising, or the station has reset itself.
                // We'll assume zero and start again.
                LOG.warn("Rain count reset. Assuming zero rain since last reading");
                rainDiff = 0;
            }
        }

        rainAbsolute = newRainAbsolute;
        rainIncrement = new UnitValue(rainDiff, unit);
    }

    public void clearRainIncrement() {

        rainIncrement = null;
    }

    /**
     * Rainfall between the last two calls to setRainAbsolute()
     * 
     * @return
     */
    public UnitValue getRainIncrement() {

        return protectNull(rainIncrement);
    }

    public UnitValue getRainAbsolute() {

        return protectNull(rainAbsolute);
    }

    public void putStationValue(String key, Object value) {

        stationValues.put(key, value);

    }

    public Map<String, Object> getStationValues() {

        return Collections.unmodifiableMap(stationValues);
    }
    
    private UnitValue protectNull(UnitValue uv) {

        if (uv == null) {
            return UnitValue.NULL;
        }
        return uv;
    }
}
